
#include <assert.h>
#include <string.h>
#include <pcre.h>
#include "parse_cfg_file.h"

#define DEFAULT_MAX_LINE_SIZE 1024

uint32_t img_ct, bbl_ct, edge_ct, img_max_idx, bbl_max_idx, edge_max_idx;
IMG_CTX images;
BBL_CTX bbls;
EDGE_LABEL edges;
_NAME_UNIVERSE registers, opcodes;

int strcmp_ptr( const void *s1, const void *s2 )
{
    return strcmp( *((char **)s1), *((char **)s2) );
}

char *name_universe_find( char *name, NAME_UNIVERSE univ )
{
    char **idx = bsearch(
        &name, univ->pool, univ->size, sizeof( univ->pool[0]), strcmp_ptr );
    if( idx )
    {
        return *idx;
    }
    return NULL;
}

char *name_universe_add( char *name, NAME_UNIVERSE univ )
{
    char **idx = bsearch(
        &name, univ->pool, univ->size, sizeof( univ->pool[0]), strcmp_ptr );
    if( idx )
    {
        return *idx;
    }
    else
    {
        char *rv;
        if( univ->size >= univ->cap )
        {
            univ->cap *= 2;
            univ->pool = realloc( univ->pool, univ->cap * sizeof( univ->pool[0] ) );
        }
        rv = univ->pool[ univ->size ] = malloc( 1 + strlen( name ) );
        strcpy( rv, name );
        ++univ->size;
        mergesort( univ->pool, univ->size, sizeof( univ->pool[0] ), strcmp_ptr );
        return rv;
    }
}

/* End canonical name universe library */

void analyze_bbl( BBL_CTX bbl )
{
    uint32_t i;
    ptr_set_open_alloced( &bbl->local_live_ins, 2 );
    ptr_set_open_alloced( &bbl->live_ins, 2 );
    ptr_set_open_alloced( &bbl->writes, 2 );
    for( i = 1; i <= bbl->ins_ct; ++i )
    {
        uint8_t j;
        INSTR ins = &bbl->inss[ bbl->ins_ct - i ];
        for( j = 0; j < ins->w_ct; ++j )
        {
            /* XXX: "[full_]name" */
            ptr_set_insert( &bbl->writes, ins->writes[ j ].full_name );
            ptr_set_subtract( &bbl->local_live_ins, ins->writes[ j ].full_name );
            ptr_set_subtract( &bbl->live_ins, ins->writes[ j ].full_name );
        }
        for( j = 0; j < ins->r_ct; ++j )
        {
            /* XXX: "[full_]name" */
            ptr_set_insert( &bbl->local_live_ins, ins->reads[ j ].full_name );
            ptr_set_insert( &bbl->live_ins, ins->reads[ j ].full_name );
        }
    }
}

void log_reg_set( PTR_SET s )
{
    size_t i;
    for( i = 0; i < s->sz; ++i )
    {
        if( i )
            printf( ", " );
        printf( "%s", (char*)s->data[i] );
    }
}

void log_bbl( BBL_CTX bbl )
{
    printf( "bbl %i F:%x Ex:%llu Is:%i\n  live_ins: ", bbl->id, bbl->flags, bbl->exec_ct, bbl->ins_ct );
    log_reg_set( &bbl->live_ins );
    printf( "\n  local_live_ins: " );
    log_reg_set( &bbl->local_live_ins );
    printf( "\n  writes: " );
    log_reg_set( &bbl->writes );
    printf( "\n" );
}

/* white space */
#define W "\\s*"
/* digits */
#define D "\\d+"
/* left and right brackets */
#define LB "\\["
#define RB "\\]"
/* quotation mark */
#define Q "\""

#define OVECCOUNT 30    /* should be a multiple of 3 */

void parse_cfg_file( FILE *f )
{
    int rc, ovector[OVECCOUNT];
    pcre *re_l_brace, *re_r_brace, *re_l_bracket, *re_r_bracket,
         *re_start, *re_images, *re_image,
         *re_bbls, *re_bbl,
         *re_ins1, *re_reg, *re_writes, *re_ins2,
         *re_edges, *re_edge;
    char line_buf[ DEFAULT_MAX_LINE_SIZE ], name_buf[ DEFAULT_MAX_LINE_SIZE ];
    int line_len;
    uint32_t k, line_ctr = 0;

    /* The CFG file happens to be in JSON format, but this code makes
     * stronger assumptions for simplicity's sake. */
    struct{ pcre **r; const char *s; } all_res[] = {
        { &re_l_brace,   "^"W"{"W"$" },
        { &re_r_brace,   "^"W"}"W",?"W"$" },
        { &re_l_bracket, "^"W LB W"$" },
        { &re_r_bracket, "^"W RB W",?"W"$" },
        { &re_start,     "^"W"{"W Q"ni"Q W":"W"("D")"W","W Q"mii"Q W":"W"("D")"W","W
                         Q"nb"Q W":"W"("D")"W","W Q"mbi"Q W":"W"("D")"W","W
                         Q"ne"Q W":"W"("D")"W","W Q"mei"Q W":"W"("D")"W","W"$" },
        { &re_images,    "^"W Q"images"Q W":"W LB W"$" },
        { &re_image,     "^"W"{"W Q"name"Q W":"W Q"([^"Q"]*)"Q W","W Q"pin_id"Q W":"W"("D")"W","
                         W Q"puid"Q W":"W"("D")"W"}"W",?"W"$" },
        { &re_bbls,      "^"W RB W","W Q"bbls"Q W":"W LB W"$" },
        { &re_bbl,       "^"W Q"id"Q W":"W"("D")"W","W Q"exec"Q W":"W"("D")"W","W Q"insN"Q W":"W"("D")"W","
                         W Q"bfl"Q W":"W"("D")"W","W Q"np"Q W":"W"("D")"W","W Q"ns"Q W":"W"("D")"W","W
                         Q"img_id"Q W":"W"("D")"W","W Q"inss"Q W":"W"$" },
        { &re_ins1,      "^"W"{"W Q"ifl"Q W":"W"("D")"W","W Q"cd"Q W":"W Q"([^"Q"]*)"Q W","
                         W Q"nr"Q W":"W"("D")"W","W Q"nw"Q W":"W"("D")"W","W Q"r"Q W":"W LB W"$" },
        { &re_reg,       "^"W"{"W Q"r"Q W":"W Q"([^"Q"]*)"Q W","W Q"rf"Q W":"W Q"([^"Q"]*)"Q W","
                         W Q"fl"Q W":"W"("D")"W"}"W",?"W"$" },
        { &re_writes,    "^"W RB W","W Q"w"Q W":"W LB W"$" },
        { &re_ins2,      "^"W RB W","W Q"ds"Q W":"W Q"([^"Q"]*)"Q W"}"W",?"W"$" },
        { &re_edges,     "^"W RB W","W Q"edges"Q W":"W LB W"$" },
        { &re_edge,      "^"W"{"W Q"id"Q W":"W"("D")"W","W Q"ect"Q W":"W"("D")"W","
                         W Q"from"Q W":"W"("D")"W","W Q"to"Q W":"W"("D")"W"}"W",?"W"$" }
    };
    size_t i, re_count = sizeof( all_res ) / sizeof( struct{ pcre **r; const char *s; } );
    for( i = 0; i < re_count; ++i )
    {
        const char *error;
        int erroffset;
        printf( "Compiling regular expression \"%s\" ...\n", all_res[i].s );
        *all_res[i].r = pcre_compile( all_res[i].s, PCRE_NEWLINE_ANY, &error, &erroffset, NULL);
        if( !*all_res[i].r )
        {
            printf( "PCRE compilation failed at offset %d: %s\n", erroffset, error );
            assert( 0 );
        }
    }

#define get_next_line \
    do { \
        char *x = fgets( line_buf, DEFAULT_MAX_LINE_SIZE, f ); \
        if( !x ) \
        { \
            printf( "file reading failure\n" ); \
            exit( 1 ); \
        } \
        line_len = strlen( line_buf ); \
        ++line_ctr; \
    } \
    while( false )

#define pcre_exec_local( REGULAR_EXPRESSION ) \
    ( rc = pcre_exec( REGULAR_EXPRESSION, NULL, line_buf, line_len, 0, PCRE_NEWLINE_ANY, ovector, OVECCOUNT ) )

#define pcre_exec_local_assert( REGULAR_EXPRESSION, EXPECTED_MATCHES ) \
    do { \
        pcre_exec_local( REGULAR_EXPRESSION ); \
        if( rc != ( EXPECTED_MATCHES ) ) \
        { \
            printf( "failed on line %i (%s)\n", line_ctr, line_buf );    \
            assert( false ); \
        } \
    } while( false )

    get_next_line;
    pcre_exec_local_assert( re_start, 7 );

    img_ct       = strtol( &line_buf[ ovector[ 2 ] ], NULL, 10 );
    img_max_idx  = strtol( &line_buf[ ovector[ 4 ] ], NULL, 10 );
    bbl_ct       = strtol( &line_buf[ ovector[ 6 ] ], NULL, 10 );
    bbl_max_idx  = strtol( &line_buf[ ovector[ 8 ] ], NULL, 10 );
    edge_ct      = strtol( &line_buf[ ovector[10 ] ], NULL, 10 );
    edge_max_idx = strtol( &line_buf[ ovector[12 ] ], NULL, 10 );
    images = calloc( img_max_idx  + 1, sizeof( images[0] ) );
    bbls   = calloc( bbl_max_idx  + 1, sizeof( bbls  [0] ) );
    edges  = calloc( edge_max_idx + 1, sizeof( edges [0] ) );

    get_next_line;
    pcre_exec_local_assert( re_images, 1 );
    printf( "Parsing images\n" );
    for( k = 0; k < img_ct; ++k )
    {
        get_next_line;
        pcre_exec_local_assert( re_image, 4 );
        uint32_t id = strtol( &line_buf[ ovector[ 6 ] ], NULL, 10 );
        assert( id <= img_max_idx );
        IMG_CTX img = &images[ id ];
        img->puid   = id;
        img->pin_id = strtol( &line_buf[ ovector[ 4 ] ], NULL, 10 );
        img->name   = malloc( 1 + ovector[ 3 ] - ovector[ 2 ] );
        strncpy( img->name, &line_buf[ ovector[ 3 ] ], ovector[ 3 ] - ovector[ 2 ] );
    }
    get_next_line;
    pcre_exec_local_assert( re_bbls, 1 );
    printf( "Parsing BBLs\n" );
    for( k = 0; k < bbl_ct; ++k )
    {
        uint32_t j, id;
        get_next_line;
        pcre_exec_local_assert( re_l_brace, 1 );
        get_next_line;
        pcre_exec_local_assert( re_bbl, 8 );
        id            = strtol( &line_buf[ ovector[ 2 ] ], NULL, 10 );
        assert( id <= bbl_max_idx );
        BBL_CTX bbl   = &bbls[ id ];
        bbl->id       = id;
        bbl->exec_ct  = strtol( &line_buf[ ovector[ 4 ] ], NULL, 10 );
        bbl->ins_ct   = strtol( &line_buf[ ovector[ 6 ] ], NULL, 10 );
        bbl->flags    = strtol( &line_buf[ ovector[ 8 ] ], NULL, 10 );
        bbl->pred_ct  = 0;
        bbl->pred_cap = strtol( &line_buf[ ovector[10 ] ], NULL, 10 );
        bbl->preds    = calloc( bbl->pred_cap, sizeof( bbl->preds[0] ) );
        bbl->succ_ct  = 0;
        bbl->succ_cap = strtol( &line_buf[ ovector[12 ] ], NULL, 10 );
        bbl->succs    = calloc( bbl->succ_cap, sizeof( bbl->succs[0] ) );
        bbl->img_puid = strtol( &line_buf[ ovector[14 ] ], NULL, 10 );
        bbl->inss = calloc( bbl->ins_ct, sizeof( bbl->inss[0] ) );
        get_next_line;
        pcre_exec_local_assert( re_l_bracket, 1 );
        for( j = 0; j < bbl->ins_ct; ++j )
        {
            uint8_t i;
            INSTR instr = &bbl->inss[ j ];

            get_next_line;
            pcre_exec_local_assert( re_ins1, 5 );
            instr->flags  = strtol( &line_buf[ ovector[ 2 ] ], NULL, 10 );
            instr->r_ct   = strtol( &line_buf[ ovector[ 6 ] ], NULL, 10 );
            instr->w_ct   = strtol( &line_buf[ ovector[ 8 ] ], NULL, 10 );
            strncpy( name_buf, &line_buf[ ovector[ 4 ] ], ovector[ 5 ] - ovector[ 4 ] );
            name_buf[ ovector[ 5 ] - ovector[ 4 ] ] = '\0';
            instr->opc    = name_universe_add( name_buf, &opcodes );
            instr->reads  = calloc( instr->r_ct, sizeof( instr->reads[0] ) );
            instr->writes = calloc( instr->w_ct, sizeof( instr->reads[0] ) );
            for( i = 0; i < instr->r_ct; ++i )
            {
                ANNOT_REG r = &instr->reads[ i ];
                get_next_line;
                pcre_exec_local_assert( re_reg, 4 );
                strncpy( name_buf, &line_buf[ ovector[ 2 ] ], ovector[ 3 ] - ovector[ 2 ] );
                name_buf[ ovector[ 3 ] - ovector[ 2 ] ] = '\0';
                r->name      = name_universe_add( name_buf, &registers );
                strncpy( name_buf, &line_buf[ ovector[ 4 ] ], ovector[ 5 ] - ovector[ 4 ] );
                name_buf[ ovector[ 5 ] - ovector[ 4 ] ] = '\0';
                r->full_name = name_universe_add( name_buf, &registers );
                r->flags = strtol( &line_buf[ ovector[ 6 ] ], NULL, 10 );
            }
            get_next_line;
            pcre_exec_local_assert( re_writes, 1 );
            for( i = 0; i < instr->w_ct; ++i )
            {
                ANNOT_REG r = &instr->writes[ i ];
                get_next_line;
                pcre_exec_local_assert( re_reg, 4 );
                strncpy( name_buf, &line_buf[ ovector[ 2 ] ], ovector[ 3 ] - ovector[ 2 ] );
                name_buf[ ovector[ 3 ] - ovector[ 2 ] ] = '\0';
                r->name      = name_universe_add( name_buf, &registers );
                strncpy( name_buf, &line_buf[ ovector[ 4 ] ], ovector[ 5 ] - ovector[ 4 ] );
                name_buf[ ovector[ 5 ] - ovector[ 4 ] ] = '\0';
                r->full_name = name_universe_add( name_buf, &registers );
                r->flags = strtol( &line_buf[ ovector[ 6 ] ], NULL, 10 );
            }
            get_next_line;
            pcre_exec_local_assert( re_ins2, 2 );
            // instr->disas = "";
            line_buf[ ovector[ 3 ] ] = '\0';
            instr->disas = strdup( &line_buf[ ovector[ 2 ] ] );
        }
        get_next_line;
        pcre_exec_local_assert( re_r_bracket, 1 );
        get_next_line;
        pcre_exec_local_assert( re_r_brace, 1 );
        analyze_bbl( bbl );
        if( k % 8000 == 17 )
        {
            log_bbl( bbl );
        }
    }
    get_next_line;
    pcre_exec_local_assert( re_edges, 1 );
    printf( "Parsing edges\n" );
    for( k = 0; k < edge_ct; k++ )
    {
        get_next_line;
        pcre_exec_local_assert( re_edge, 5 );
        uint32_t id      = strtol( &line_buf[ ovector[ 2 ] ], NULL, 10 );
        assert( id <= edge_max_idx );
        EDGE_LABEL edge  = &edges[ id ];
        edge->id         = id;
        edge->exec_ct    = strtol( &line_buf[ ovector[ 4 ] ], NULL, 10 );
        uint32_t id_from = strtol( &line_buf[ ovector[ 6 ] ], NULL, 10 );
        assert( id_from <= bbl_max_idx );
        uint32_t id_to   = strtol( &line_buf[ ovector[ 8 ] ], NULL, 10 );
        assert( id_to <= bbl_max_idx );
        BBL_CTX bbl_from = &bbls[ id_from ], bbl_to = &bbls[ id_to ];
        assert( bbl_from->succ_ct < bbl_from->succ_cap );
        assert( bbl_to->pred_ct   < bbl_to->pred_cap );
        if( bbl_to->id != id_to || bbl_from->id != id_from || id_to == 0 || id_from == 0
            || id_to == 80501 || id_from == 80501 )
        {
            printf( "la edge:%p from:%i %i %p %p   to:%i %i %p %p   bbl_from->succ_ct: %i   bbl_to->pred_ct: %i\n",
                    edge, bbl_from->id, id_from, bbl_from, bbl_from->succs, bbl_to->id, id_to, bbl_to, bbl_to->preds,
                    bbl_from->succ_ct, bbl_to->pred_ct );
            // abort();
        }
        bbl_from->succs[ bbl_from->succ_ct ].bbl   = bbl_to->id;
        bbl_from->succs[ bbl_from->succ_ct ].label = edge->id;
        ++bbl_from->succ_ct;
        bbl_to  ->preds[ bbl_to  ->pred_ct ].bbl   = bbl_from->id;
        bbl_to  ->preds[ bbl_to  ->pred_ct ].label = edge->id;
        ++bbl_to->pred_ct;
        ptr_set_open_alloced( &edge->live_in_to_succ, 2 );
    }
    get_next_line;
    pcre_exec_local_assert( re_r_bracket, 1 );
    get_next_line;
    pcre_exec_local_assert( re_r_brace, 1 );


    for( k = 0; k <= bbl_max_idx; ++k )
    {
        BBL_CTX bbl = &bbls[ k ];
        if( !bbl->id )
            continue;
        if( ( bbl->pred_ct != bbl->pred_cap || bbl->succ_ct != bbl->succ_cap )
            && !( bbl->id == 11 && bbl->pred_ct == 0 && bbl->succ_ct == 1 ) )
        {
            printf( "caps and counts %i   %i %i  %i %i\n", bbl->id, bbl->pred_ct, bbl->pred_cap,
                    bbl->succ_ct, bbl->succ_cap );
            // abort();
        }
    }
}

#undef W
#undef D
#undef LB
#undef RB
#undef Q
